#include "test41_MyUtilsSet.h"




// 实用小工具集合

void test41_MyUtilsSet::classMain()
{
	std::cout << "test41_MyUtilsSet\n";
	//IpUtils::ipTest();
	FileUtils::folderTest();
}

////////////////////////////////////////////////////////////// IpUtils
void IpUtils::ipTest()
{
	std::cout << "ipTest\n";

	std::string ip = "192.168.1.112";
	std::string mask = "255.255.255.0";
	std::string subnetPrefix;
	int prefixLength;
	//IpUtils::calculateSubnetPrefix(ip,"255.255.255.255", subnetPrefix, prefixLength);
	IpUtils::calculateSubnetPrefix(ip,mask, subnetPrefix, prefixLength);
	std::cout << "ip=" << ip << " mask=" << mask << " 子网前缀=" << subnetPrefix << " 子网前缀长度=" << prefixLength << "\n";
}

// 将十进制 IP 地址转换为二进制字符串
std::string IpUtils::decimalToBinary(std::string decimalAddress) {
	std::string binaryAddress = "";
	// stol = 将字符串str转成 long 整数 
	// stoll = 将字符串str转成 long long 整数
	int num = stoi(decimalAddress); // 将字符串转成int整数，默认为十进制，还可转成8，16进制
	for (int i = 7; i >= 0; i--) { // 8位
		auto mytmp = num >> i; // 算术移位
		auto tmp = (num >> i) & 1; // 按位and = 按位与
		binaryAddress += std::to_string((num >> i) & 1); // 十进制转二进制 = 除2取余，逆序排列
	}
	return binaryAddress;
}

// 将二进制 IP 地址转换为十进制字符串
std::string IpUtils::binaryToDecimal(std::string binaryAddress) {
	std::string decimalAddress = "";
	// bitset = 在 bitset 头文件中，它是一种类似数组的结构，每一个元素只能是０或１，每个元素仅用１bit空间，占用空间相当于1/8个char元素
	// to_ulong() = 将 bitset 的内容转换为无符号长整数，返回一个 unsigned long 的整数值，该整数值与 bitset 具有相同的位设置
	int num = std::bitset<8>(binaryAddress).to_ulong();
	decimalAddress = std::to_string(num);
	return decimalAddress;
}

// 获取子网掩码的二进制形式 = 没用
std::string IpUtils::getSubnetMask(std::string subnetMask) {
	std::string binaryMask = "";
	int num = stoi(subnetMask);
	for (int i = 0; i < num; i++) {
		binaryMask += "1";
	}
	for (int i = num; i < 32; i++) {
		binaryMask += "0";
	}
	return binaryMask;
}

// 计算子网前缀
std::string IpUtils::calculateSubnetPrefix(std::string ipAddress, std::string subnetMask, std::string& subnetPrefix, int& prefixLength) {
	// 将 IP 地址和子网掩码转换为二进制形式
	std::string binaryIp = "", binaryMask = "";
	std::stringstream sIp(ipAddress); // string流
	std::stringstream sMask(subnetMask);
	while (sIp.good()) { // ip转二进制
		std::string decimalAddress;
		std::getline(sIp, decimalAddress, '.');
		binaryIp += decimalToBinary(decimalAddress);
	}
	binaryMask = getSubnetMask(subnetMask);
	std::string binaryMask2 = "";
	while (sMask.good()) { // 子网掩码转二进制
		std::string decimalAddress;
		std::getline(sMask, decimalAddress, '.');
		binaryMask2 += decimalToBinary(decimalAddress);
	}

	// 对 IP 地址和子网掩码进行按位与操作，得到子网前缀
	subnetPrefix = "";
	for (int i = 0; i < 32; i++) {
		if (binaryIp[i] == '1' && binaryMask2[i] == '1') { // 按位与=都为1则结果1，有0则结果0
			subnetPrefix += "1";
		}
		else {
			subnetPrefix += "0";
		}
	}


	// 去除子网前缀末尾的0
	int rightZer0Num = 0; // 末尾0的个数 = 子网后缀长度
	for (int i = binaryMask2.size() - 1; i >= 0; --i) { // 从右向左遍历
		if (binaryMask2[i] == '0')
		{
			rightZer0Num++;
		}
		else
		{
			// 发现第一个不是0的则退出循环
			break;
		}
	}
	int fixLength = 32 - rightZer0Num; // 子网前缀长度=32-后缀的0的个数
	// 计算子网前缀长度
	//prefixLength = stoi(subnetMask);
	prefixLength = fixLength;


	// 将子网前缀转换为十进制形式并返回
	std::string decimalPrefix = "";
	for (int i = 0; i < 4; i++) {
		decimalPrefix += binaryToDecimal(subnetPrefix.substr(i * 8, 8)) + ".";
	}
	decimalPrefix = decimalPrefix.substr(0, decimalPrefix.length() - 1);
	return decimalPrefix;
}

////////////////////////////////////////////////////////////// IpUtils

////////////////////////////////////////////////////////////// FileUtils

void FileUtils::folderTest()
{
	std::cout << "folderTest\n";
	//std::string path = "D:\\project\\vs2019Project";
	std::string path;
	std::cout << "输入文件夹路径：\n";
	std::cin >> path;
	//voisFolder(path);

	//voisFolderWithLevel(path,2);
	myVoisFolderWithLevel(path, 2);
}

// 文件的大小和所占空间


// 关于文件和所占空间
//我曾经无聊，把一个空文本文档
//
//复制进去另一个空文件夹，再把这个文件夹复制个几十份复制进另一个文件夹，再把这文件夹复制个几十份，循环许多次之后，
//就和象棋上摆米粒一样，1KB的文本文档不一会就被我造到了1个G，我只要再复制一次，就可以几十个G了。
//
//我这样无聊的制造了一个超大的垃圾，我也不记得大到什么程度了，反正最后一次复制的时候，360直接跳出来了，提示我风险了

// 

// 递归遍历文件夹
// 参数中const和&的作用
//		const 关键字表示该参数在函数内部不会被修改
//		& 表示传递的是引用，而不是值。这样可以减少对象的复制开销，提高程序的性能
// 文件流和文件句柄
void FileUtils::voisFolder(const std::string& folderPath)
{
	// win下的路径有盘符
	std::string searchPath = folderPath + "\\*";
	struct _finddata_t fileInfo;
	intptr_t handle = _findfirst(searchPath.c_str(), &fileInfo); // _findfirst 函数打开路径，并将结果保存到_finddata_t类型的结构体变量fileInfo中。fileInfo同意是个引用
	if (handle == -1) { // -1表示文件夹打开失败
		std::cout << "文件夹打开失败: " << folderPath << std::endl;
		return;
	}

	do {
		if (strcmp(fileInfo.name, ".") != 0 && strcmp(fileInfo.name, "..") != 0) { // 判断文件名是否为 . 或 ..
			std::string filePath = folderPath + "\\" + fileInfo.name; // 当前遍历的路径

			if (fileInfo.attrib & _A_SUBDIR) { // fileInfo.attrib 判断当前项是文件还是文件夹
				// 文件夹
				std::cout << "文件夹: " << filePath << std::endl;
				voisFolder(filePath); // 深度优先遍历
			}
			else {
				// 文件

				__int64 fileSize = fileInfo.size;
				std::cout << "文件: " << filePath << " fileSize=" << fileSize << " bytes" << std::endl;
			}
		}
	} while (_findnext(handle, &fileInfo) == 0); // _findnext 函数获取下一个文件或文件夹的信息，返回0，则表示还有文件或文件夹可以遍历

	_findclose(handle); // 关闭文件
}

// 按指定分隔符切分字符串
std::vector<std::string> FileUtils::splitString(const std::string& str, const std::string& delimiter) {
	std::vector<std::string> result;
	size_t pos = 0;
	size_t prevPos = 0;

	while ((pos = str.find(delimiter, prevPos)) != std::string::npos) {
		std::string token = str.substr(prevPos, pos - prevPos);
		result.push_back(token);
		prevPos = pos + delimiter.length();
	}

	std::string lastToken = str.substr(prevPos);
	result.push_back(lastToken);

	return result;
}

// 自定义比较函数，用于按照 curLevel 升序排序
bool FileUtils::compareByCurLevel(const FileUtils::myFolderInfo& a, const FileUtils::myFolderInfo& b) {
	return a.curLevel < b.curLevel;
}

// 带层级的递归遍历 = 遍历当前目录下的文件夹和文件，默认是按字母顺序遍历
void FileUtils::voisFolderWithLevel(const std::string& folderPath, int curLevel, 
	std::list<FileUtils::myFolderInfo>& folderList)
{	
	// win下的路径有盘符
	std::string searchPath = folderPath + "\\*";
	struct _finddata_t fileInfo;
	intptr_t handle = _findfirst(searchPath.c_str(), &fileInfo); // _findfirst 函数打开路径，并将结果保存到_finddata_t类型的结构体变量fileInfo中。fileInfo同意是个引用
	if (handle == -1) { // -1表示文件夹打开失败
		std::cout << "文件夹打开失败: " << folderPath << std::endl;
		return;
	}

	do {
		if (strcmp(fileInfo.name, ".") != 0 && strcmp(fileInfo.name, "..") != 0) { // 判断文件名是否为 . 或 ..
			std::string curPath = folderPath + "\\" + fileInfo.name; // 当前遍历的路径
			int curFileTotalSize = 0; // 当前文件下文件总大小

			// fileInfo.attrib的值与 _A_SUBDIR 进行按位与运算。如果结果为非零（即为真），则表示该项是一个文件夹；如果结果为零（即为假），则表示该项是一个文件
			if (fileInfo.attrib & _A_SUBDIR) { // _A_SUBDIR 是 io.h 头文件中定义的常量，表示文件夹的属性
				// 文件夹
				__int64 folderSize = fileInfo.size;
				myFolderInfo curFolder;
				curFolder.curLevel = curLevel;
				curFolder.curPath = curPath;
				curFolder.type = "folder";
				curFolder.curSize = 0; // 大小写到上一级的路径

				folderList.push_front(curFolder);
				//std::cout << "文件夹: " << curPath << " fileSize=" << folderSize << " bytes" << std::endl;
				voisFolderWithLevel(curPath,curLevel+1, folderList); // 深度优先遍历
			}
			else {
				// 文件
				__int64 fileSize = fileInfo.size;
				myFolderInfo curFolder;
				curFolder.curLevel = curLevel;
				curFolder.curPath = curPath;
				curFolder.type = "file";
				curFolder.curSize = fileSize;
				folderList.push_front(curFolder);
				//curFileTotalSize += fileSize;
				//std::cout << "文件: " << curPath << " fileSize=" << fileSize << " bytes" << " curFileTotalSize=" << curFileTotalSize << std::endl;
			}
		}
	} while (_findnext(handle, &fileInfo) == 0); // _findnext 函数获取下一个文件或文件夹的信息，返回0，则表示还有文件或文件夹可以遍历

	_findclose(handle); // 关闭文件
}

// 通过参数控制文件夹遍历的层级
// 先深度遍历获取最文件嵌套层数，每层文件夹所占空间，每层文件数量等信息
// 构造一个二级list = 一级表示当前所在层数，二级表示当前层所有的文件夹
// 根据level选择返回的文件夹大小
void FileUtils::myVoisFolderWithLevel(const std::string& folderPath, const int& level)
{

	std::list<FileUtils::myFolderInfo> folderList;
	
	std::vector<std::string> token = FileUtils::splitString(folderPath, "\\");
	int inputPathLevel = token.size(); // 传入路径的level
	voisFolderWithLevel(folderPath, inputPathLevel,folderList);
	
	// 对 folderList 按照 curLevel 进行升序排序
	folderList.sort(FileUtils::compareByCurLevel);

	// 使用 map 容器来对 folderList 按照 curLevel 进行分类
	std::map<int, std::list<myFolderInfo>> folderMap;
	for (const auto& folder : folderList) {
		folderMap[folder.curLevel].push_back(folder); // 1,myFolderInfo
	}
	// 计算文件夹大小
	// 使用 map 容器来对 folderList 按照 curLevel 进行分类并统计 curSize 总和
	std::map<int, int> sizeSumMap;
	for (const auto& folder : folderList) {
		sizeSumMap[folder.curLevel] += folder.curSize;
	}

	// 遍历分类后的 folderMap
	for (const auto& pair : folderMap) {
		std::cout << "curLevel: " << pair.first << std::endl;
		for (const auto& folder : pair.second) {
			std::cout << "    curPath: " << folder.curPath << ", curSize: " << folder.curSize << std::endl;
		}
	}


	// 适用迭代器遍历folderList
	for (auto it : folderList)
	{
		std::cout << "folderList= " << it.curLevel << it.curPath << it.curSize << it.type << "\n";
	}
	//std::cout << folderList.begin() << "\n";
}




////////////////////////////////////////////////////////////// FileUtils